﻿using System;

namespace ConsoleApplication16
{
    delegate Tuple<T, Chain<T>> Chain<T>();

    static class ChainExtensions
    {
        public static Chain<TResult> Chain<T, TResult>(this T arg, Func<T, Tuple<TResult, T>> func)
        {
            return () => {
                var tuple = func(arg);
                return Tuple.Create(tuple.Item1, Chain(tuple.Item2, func));
            };
        }

        public static Chain<TResult> Select<TSource, TResult>(this Chain<TSource> it, Func<TSource, TResult> selector)
        {
            return it.Chain(chain => {
                var tuple = chain();
                return Tuple.Create(selector(tuple.Item1), tuple.Item2);
            });
        }

        public static Chain<TSource> Skip<TSource>(this Chain<TSource> it, int count)
        {
            while (count > 0)
            {
                it = it().Item2;
                --count;
            }
            return it;
        }
    }

    class EnumerableProgram
    {
        public static void Main2()
        {
            Console.WriteLine(monte_carlo(cesaro_stream())
                .Select(p => Math.Sqrt(6/p))
                .Skip(10000000 - 1)().Item1);
        }

        static Chain<int> random_numbers()
        {
            const int random_init = 0;
            return random_init.Chain(seed => Tuple.Create(seed, rand_update(seed)));
        }

        static Chain<bool> cesaro_stream()
        {
            return random_numbers().Chain(seed => {
                var tuple1 = seed();
                var tuple2 = tuple1.Item2();
                return Tuple.Create(gcd(tuple1.Item1, tuple2.Item1) == 1, tuple2.Item2);
            });
        }

        static Chain<double> monte_carlo(Chain<bool> experiment)
        {
            return new {experiment, passed = 0, failed = 0}.Chain(seed => {
                int passed;
                int failed;
                var tuple = seed.experiment();
                if (tuple.Item1)
                {
                    passed = seed.passed + 1;
                    failed = seed.failed;
                }
                else
                {
                    passed = seed.passed;
                    failed = seed.failed + 1;
                }
                return Tuple.Create((double) passed/(passed + failed), new {experiment = tuple.Item2, passed, failed});
            });
        }

        // ReSharper disable UnusedParameter.Local
        private static int rand_update(int i)
            // ReSharper restore UnusedParameter.Local
        {
            return random.Next();
        }

        static int gcd(int a, int b)
        {
            while (b != 0)
                b = a % (a = b);
            return a;
        }

        static readonly Random random = new Random();
    }
}